package com.zhangkang.stock.stocklib.tools

import com.zhangkang.stock.stocklib.data.KDJ
import kotlin.math.max
import kotlin.math.min

/**
 * @author : Android-张康
 * created on: 2023/5/9 09:44
 * description:
 */
class KDJTools(private val X: Int, private val Y: Int, private val Z: Int) {

    private val defaultK = 50F
    private val defaultD = 50F

    /**
     * 计算KDJ
     *
     * @param data 当前的数据
     * @param currentIndex 当前数据
     * @param previousKDJ 上一个周期的KDJ值
     * @param closePrice 收盘价
     * @param getMaxPrice 获取最大值
     * @param getMinPrice 获取最小值
     */
    fun <T> calculationKDJ(
        data: List<T>,
        currentIndex: Int,
        previousKDJ: KDJ?,
        closePrice: Float,
        getMaxPrice: (T) -> Float,
        getMinPrice: (T) -> Float
    ): KDJ {
        var maxPrice = getMaxPrice(data[currentIndex])
        var minPrice = getMinPrice(data[currentIndex])
        val end = currentIndex - 1
        var start = end - X + 1
        if (start < 0) {
            start = 0
        }
        for (index in start..end) {
            val currentMaxPrice = getMaxPrice(data[index])
            val currentMinPrice = getMinPrice(data[index])
            if (currentMaxPrice > maxPrice) {
                maxPrice = currentMaxPrice
            }
            if (currentMinPrice < minPrice) {
                minPrice = currentMinPrice
            }
        }
        return calculationKDJ(closePrice, maxPrice, minPrice, previousKDJ)
    }

    /**
     * 计算KDJ
     *
     * @param closePrice 收盘价
     * @param maxPrice [X] 日内最高价
     * @param minPrice [X] 日内最低价
     * @param previousKDJ 上一个周期的KDJ值
     */
    private fun calculationKDJ(
        closePrice: Float,
        maxPrice: Float,
        minPrice: Float,
        previousKDJ: KDJ?
    ): KDJ {
        val RSV = RSV(closePrice, maxPrice, minPrice)
        //异常情况K和D值的范围一般为0-100，部分特殊行情可能会超出这个范围，特别是新股刚上市的几天；
        // 基于“钝化现象”，我们规定：当K值和D值计算结果大于100.00时，显示为100.00；
        // 当K值和D值计算结果小于0.00时，显示为0.00；
        // J值不做任何限制，它可能是个很大的数值，也可能是个负数；
        var K = K(previousKDJ?.K ?: defaultK, RSV)
        K = min(K, 100F)
        K = max(K, 0F)
        var D = D(previousKDJ?.D ?: defaultD, K)
        D = min(D, 100F)
        D = max(D, 0F)
        val J = J(K, D)
        return KDJ(K, D, J)
    }

    /**
     * 计算RSV未成熟随机值
     * RSV = （收盘价 - 最低价） / （最高价 - 最低价） * 100
     * 1、其中，收盘价、最低价、最高价的取值范围为X周期内；
     * 2、如，当X=9时，即“当日+前移8个交易日”共9个交易日中的收盘价、最低价、最高价；
     * 3、当日未出收盘价前，将最新价视为收盘价，代入公式计算；
     * 4、向前取值时，若数据不足9个交易日，亦不影响计算（如，新股刚上市交易5天，我们要计算第五天的RSV，但数据不足9天，则取5天哪的收盘价、最低价、最高价进行计算即可）
     */
    private fun RSV(closePrice: Float, maxPrice: Float, minPrice: Float): Float {
        return (closePrice - minPrice) / (maxPrice - minPrice) * 100
    }

    /**
     * 计算K值
     * K值 = [（Y-1）/ Y ] * 前一周期K值 + （1 / Y）* 当周期RSV值
     * 若无前一日K值与D值，则一般分别用50代替。
     */
    private fun K(previousK: Float, RSV: Float): Float {
        return (Y - 1.0F) / Y * previousK + 1.0F / Y * RSV
    }

    /**
     * 计算D值
     * D值 = [（Z-1）/ Y ] * 前一周期D值 + （1 / Z）* 当周期K值
     * 若无前一日K值与D值，则一般分别用50代替。
     */
    private fun D(previousD: Float, K: Float): Float {
        return (Z - 1.0F) / Y * previousD + 1.0F / Z * K
    }

    /**
     * 计算J值
     * J值 =3 * K值 - 2*d值
     */
    private fun J(K: Float, D: Float): Float {
        return 3 * K - 2 * D
    }
}